Long only 1/n portfolio#

import pandas as pd
pd.options.plotting.backend = "plotly"

import yfinance as yf

from cvx.simulator.builder import builder
from cvx.simulator.grid import resample_index
data = yf.download(tickers = "SPY AAPL GOOG MSFT",  # list of tickers
                   period = "10y",                   # time period
                   interval = "1d",                 # trading interval
                   prepost = False,                 # download pre/post market hours data?
                   repair = True)                   # repair obvious price errors e.g. 100x?
[                       0%%                      ]
[**********************50%%                      ]  2 of 4 completed
[**********************75%%**********            ]  3 of 4 completed
[*********************100%%**********************]  4 of 4 completed

prices = data["Adj Close"]
capital = 1e6
b = builder(prices=prices, initial_cash=capital)

for time, state in b:
    # each day we invest a quarter of the capital in the assets
    b[time[-1]] = 0.25 * state.nav / state.prices
portfolio = b.build()
portfolio.profit.cumsum().plot()
/home/runner/work/cvxmarkowitz/cvxmarkowitz/.venv/lib/python3.10/site-packages/_plotly_utils/basevalidators.py:105: FutureWarning: The behavior of DatetimeProperties.to_pydatetime is deprecated, in a future version this will return a Series containing python datetime objects instead of an ndarray. To retain the old behavior, call `np.array` on the result
  v = v.dt.to_pydatetime()
portfolio.nav.plot()
/home/runner/work/cvxmarkowitz/cvxmarkowitz/.venv/lib/python3.10/site-packages/_plotly_utils/basevalidators.py:105: FutureWarning:

The behavior of DatetimeProperties.to_pydatetime is deprecated, in a future version this will return a Series containing python datetime objects instead of an ndarray. To retain the old behavior, call `np.array` on the result

Rebalancing#

Usually we would not execute on a daily basis but rather rebalance every week, month or quarter. There are two approaches to deal with this problem in cvxsimulator.

  • Resample the existing daily portfolio (helpful to see effect of your hesitated trading)

  • Trade only on days that are within a predefined grid (most flexible if you have a rather irregular grid)

Resample an existing portfolio#

portfolio_resampled = portfolio.resample(rule="M")
frame = pd.DataFrame({"original": portfolio.nav, "monthly": portfolio_resampled.nav})
frame
original monthly
Date
2013-09-30 1.000000e+06 1.000000e+06
2013-10-01 1.013276e+06 1.013276e+06
2013-10-02 1.016715e+06 1.016715e+06
2013-10-03 1.007323e+06 1.007338e+06
2013-10-04 1.008106e+06 1.008123e+06
... ... ...
2023-09-21 7.668447e+06 7.647216e+06
2023-09-22 7.656891e+06 7.634961e+06
2023-09-25 7.695687e+06 7.673543e+06
2023-09-26 7.550094e+06 7.528283e+06
2023-09-27 7.567292e+06 7.546341e+06

2516 rows × 2 columns

print(portfolio_resampled.stocks)
                    AAPL          GOOG         MSFT          SPY
Date                                                            
2013-09-30  16823.525108  11459.491312  8970.491796  1785.291266
2013-10-01  16655.259892  11466.452379  9008.379869  1794.785459
2013-10-02  16655.259892  11466.452379  9008.379869  1794.785459
2013-10-03  16655.259892  11466.452379  9008.379869  1794.785459
2013-10-04  16655.259892  11466.452379  9008.379869  1794.785459
...                  ...           ...          ...          ...
2023-09-21  10621.688080  14710.417033  6122.999673  4475.904724
2023-09-22  10621.688080  14710.417033  6122.999673  4475.904724
2023-09-25  10621.688080  14710.417033  6122.999673  4475.904724
2023-09-26  10621.688080  14710.417033  6122.999673  4475.904724
2023-09-27  10621.688080  14710.417033  6122.999673  4475.904724

[2516 rows x 4 columns]
# almost hard to see that difference between the original and resampled portfolio
frame.plot()
/home/runner/work/cvxmarkowitz/cvxmarkowitz/.venv/lib/python3.10/site-packages/_plotly_utils/basevalidators.py:105: FutureWarning:

The behavior of DatetimeProperties.to_pydatetime is deprecated, in a future version this will return a Series containing python datetime objects instead of an ndarray. To retain the old behavior, call `np.array` on the result
# number of shares traded
portfolio_resampled.trades_stocks.iloc[1:].plot()
/home/runner/work/cvxmarkowitz/cvxmarkowitz/.venv/lib/python3.10/site-packages/_plotly_utils/basevalidators.py:105: FutureWarning:

The behavior of DatetimeProperties.to_pydatetime is deprecated, in a future version this will return a Series containing python datetime objects instead of an ndarray. To retain the old behavior, call `np.array` on the result

Trade only days in predefined grid#

b = builder(prices=prices, initial_cash=capital)

# define a grid
grid = resample_index(prices.index, rule="M")

for time, state in b:
    # each day we invest a quarter of the capital in the assets
    if time[-1] in grid:
        b[time[-1]] = 0.25 * state.nav / state.prices
    else:
        # forward fill an existing position
        b[time[-1]] = b[time[-2]]
        
portfolio = b.build()
portfolio.nav.plot()
/home/runner/work/cvxmarkowitz/cvxmarkowitz/.venv/lib/python3.10/site-packages/_plotly_utils/basevalidators.py:105: FutureWarning:

The behavior of DatetimeProperties.to_pydatetime is deprecated, in a future version this will return a Series containing python datetime objects instead of an ndarray. To retain the old behavior, call `np.array` on the result
# Trading only once a month can lead to days where 150k had to be reallocated
portfolio.turnover.iloc[1:].plot()
/home/runner/work/cvxmarkowitz/cvxmarkowitz/.venv/lib/python3.10/site-packages/_plotly_utils/basevalidators.py:105: FutureWarning:

The behavior of DatetimeProperties.to_pydatetime is deprecated, in a future version this will return a Series containing python datetime objects instead of an ndarray. To retain the old behavior, call `np.array` on the result

Why not resampling the prices?#

I don’t believe in bringing the prices to a monthly grid. This would render it hard to construct signals given the sparse grid. We stay on a daily grid and trade once a month.